package mx.itesm.pruebadmescenas;

import java.util.LinkedList;

import org.andengine.engine.camera.hud.controls.AnalogOnScreenControl;
import org.andengine.engine.camera.hud.controls.BaseOnScreenControl;
import org.andengine.engine.camera.hud.controls.AnalogOnScreenControl.IAnalogOnScreenControlListener;
import org.andengine.engine.handler.IUpdateHandler;
import org.andengine.entity.Entity;
import org.andengine.entity.primitive.Rectangle;
import org.andengine.entity.scene.background.AutoParallaxBackground;
import org.andengine.entity.scene.background.ParallaxBackground.ParallaxEntity;
import org.andengine.entity.sprite.ButtonSprite;
import org.andengine.entity.sprite.Sprite;
import org.andengine.input.touch.TouchEvent;

import android.content.Context;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;

/**
 * El juego, aquí va toda la lógica del juego. Dentro de la escena se da el comportamiento para cada objeto.
 * 
 * @author Roberto Martínez Román
 *
 */
public class EscenaJuego extends EscenaBase implements SensorEventListener // Registra eventos del sensor de gravedad
{
	private Sprite personaje;	// El personaje
	private Sprite enemigo;		// Un enemigo independiente
	
	private Entity pantallaPausa;	// Se superpone a la pantalla cuando se pausa el juego
	
	// *** Estado del juego HUD
	private EstadoJuego estado;
	// La lista de balas 
	private LinkedList<Sprite> listaBalas;	
	// Lista de enemigos
	private LinkedList<Sprite> listaEnemigos;
	// Acumulador de tiempo para generar un enemigo cada 3 segundos
	private float tiempo;
	
	// Pausa (indica si el jego esta pausado o no)
	private boolean pausado=false;
	
	// Control/joystick
	private AnalogOnScreenControl controlVirtual;
	
	// Botón de pausa
	private ButtonSprite btnPausa;
	
	// **** SENSOR DE GRAVEDAD
	private SensorManager mSensorManager;	// Administrador de sensores
	private Sensor sensorGravedad;			// El sensor de gravedad que nos interesa
	
	// PREFERENCIAS DE USUARIO
	private final String MARCADOR = "Marcador";
	
	@Override
	public void crearEscena() {
		
		// Inicializa la escena
		agregarFondo();
		agregarObjetos();	// Personaje, items, etc.
		agregarControles(); // joystick virtual
		// Agregar el marcador
		agregarMarcador();
		// Agregar el botón de pausa
		agregarBotonPausa();
		// Lógica del juego
		animarElementos();
		// Inicializa el sensor de gravedad
		iniciarSensorGravedad();
		// Leer el marcador anterior más alto
		leerMarcadorAnterior();
	}

	// PREFERENCIAS: Recupera el marcador almacenado en las preferencias y con ese valor inicia los puntos (sólo como prueba)
	private void leerMarcadorAnterior() {
		SharedPreferences preferencias = actividad.getSharedPreferences(MARCADOR, 0);
		int puntos = preferencias.getInt(MARCADOR, 0); // Recupera los puntos almacenados; si no hay, regresa 0
		estado.aumentarMarcador(puntos);	// El puntaje inicia con este valor (sólo como prueba)
	}

	// Crea y regresa la pantalla de pausa, sólo tiene el botón de reanudar
	private Entity crearPantallaPausa() {
		Rectangle rectPausa = new Rectangle(ProyectoJuego.ANCHO_CAMARA / 2,
				ProyectoJuego.ALTO_CAMARA / 2, ProyectoJuego.ANCHO_CAMARA - 64,
				ProyectoJuego.ALTO_CAMARA - 64, vbom);
		rectPausa.setColor(0, 0, 0, 0.5f);

		// Crea el botón de REANUDAR y lo agrega al cuadro de pausa
		ButtonSprite btnReanudar = new ButtonSprite(
				ProyectoJuego.ANCHO_CAMARA / 2,
				ProyectoJuego.ALTO_CAMARA / 2,
				admRecursos.btnPausaRegion, vbom) {

			// Se ejecuta cada vez que el usuario hace tap sobre el botón
			@Override
			public boolean onAreaTouched(TouchEvent evento,
					float pTouchAreaLocalX, float pTouchAreaLocalY) {
				if (evento.isActionDown()) {
					reanudarJuego();
				}
				return super.onAreaTouched(evento, pTouchAreaLocalX,
						pTouchAreaLocalY);
			}
		};
		this.registerTouchArea(btnReanudar);
		rectPausa.attachChild(btnReanudar);
		return rectPausa;
	}

	// Agrega el botón de PAUSA a la escena
	private void agregarBotonPausa() {
		// Habilitar los eventos de touch en la escena
		this.setTouchAreaBindingOnActionDownEnabled(true);

		// Crea el botón PAUSA y lo agrega a la escena (arriba a la izquierda)
		btnPausa = new ButtonSprite(admRecursos.btnPausaRegion.getWidth(),
				ProyectoJuego.ALTO_CAMARA-admRecursos.btnPausaRegion.getHeight(),
				admRecursos.btnPausaRegion, vbom) {
			// Se ejecuta cada vez que el usuario hace tap sobre el botón
			@Override
			public boolean onAreaTouched(TouchEvent pSceneTouchEvent,
					float pTouchAreaLocalX, float pTouchAreaLocalY) {
				if (pSceneTouchEvent.isActionDown()) {
					pausado = true;	// Entra en estado de pausado
					pantallaPausa = crearPantallaPausa();	// Crea la pantalla de pausa
					camara.setChaseEntity(null);
					EscenaJuego.this.attachChild(pantallaPausa); // Agrega la pantalla de pausa
					camara.setHUD(null); // Esconde la HUD
					controlVirtual.setVisible(false); // Esconde el joystick
					btnPausa.setVisible(false);	// Esconde el botón de pausa
				}
				return super.onAreaTouched(pSceneTouchEvent, pTouchAreaLocalX,
						pTouchAreaLocalY);
			}
		};
		this.registerTouchArea(btnPausa);
		this.attachChild(btnPausa);
	}


	// Configura el fondo del JUEGO
	private void agregarFondo() {
		final AutoParallaxBackground autoParallaxBackground = new AutoParallaxBackground(0, 0, 0, 5);
		this.setBackground(autoParallaxBackground);
		this.setBackgroundEnabled(true);
		// Agregar a cargarRecursoJuego la carga de las imágenes de fondo
		final Sprite fondo = new Sprite(ProyectoJuego.ANCHO_CAMARA/2, ProyectoJuego.ALTO_CAMARA/2, 
				admRecursos.fondoJuegoRegion, vbom);
		autoParallaxBackground.attachParallaxEntity(new ParallaxEntity(-3.0f, fondo));
	}

	// Agrega a la escena del juego el HUD
	private void agregarMarcador() {
		estado = new EstadoJuego(0, engine);
		camara.setHUD(estado);
	}

	/**
	 * Agrega los objetos iniciales en la escena
	 */
	private void agregarObjetos() {
		
		// LISTA de balas y enemigos vacías
		listaBalas = new LinkedList<Sprite>();
		listaEnemigos = new LinkedList<>();

		// Se crea el personaje y se agrega centrado en la parte de abajo
		personaje = new Sprite(ProyectoJuego.ANCHO_CAMARA/2, 
				admRecursos.personajeRegion.getHeight()/2, admRecursos.personajeRegion, vbom);
		attachChild(personaje);

		// Se crea un enemigo a un octavo de la pantalla del margen izquierdo y a 3/4 de altura
		enemigo = new Sprite(ProyectoJuego.ANCHO_CAMARA/8, 
				3*ProyectoJuego.ALTO_CAMARA/4, admRecursos.enemigoRegion, vbom);
		attachChild(enemigo);
	}

	// AQUÍ va la LOGICA completa del JUEGO
	private void animarElementos() {
		
		// Este objeto ejecuta el método onUpdate un número de veces aproximado a los FPS
		registerUpdateHandler(new IUpdateHandler() {
			
			@Override
			public void onUpdate(float pSecondsElapsed) {
				if (pausado) {
					return; // Esta pausado, no hacer modificaciones en la posición
				}
				
				// Acumular el tiempo
				tiempo += pSecondsElapsed;

				if ( tiempo>=3.0f ) {	// Ya pasaron 3 segundos?
					tiempo = 0;
					// Coordenadas aleatorias
					float px = (float)(Math.random()*ProyectoJuego.ANCHO_CAMARA);
					float py =  (float)(Math.random()*ProyectoJuego.ALTO_CAMARA/2+ProyectoJuego.ALTO_CAMARA/2);
					Sprite enemigo = new Sprite(px, py, admRecursos.enemigoRegion, vbom);
					attachChild(enemigo);	// Se agrega a la escena y...
					listaEnemigos.add(enemigo); // ...se agrega a la lista también
				}
				// ANIMAR a los enemigos
				for (Sprite e:listaEnemigos) {
					e.setRotation(e.getRotation()+4); // Cada enemigo rota 4 grados
				}
				// PROBAR CHOQUES
				for(int i=listaBalas.size()-1; i>=0; i--){
					Sprite bala = listaBalas.get(i);	// Cada bala se prueba....
					for(int k=listaEnemigos.size()-1;k>=0; k--) {
						Sprite e = listaEnemigos.get(k); // ...con cada enemigo
						if ( bala.collidesWith(e) ) { // chocaron??
							listaEnemigos.remove(k);		// Quitarlo de la lista
							detachChild(e);				// Quitarlo de la escena
							estado.aumentarMarcador(1);	// El estado se encarga de actualizar la pantalla
						}
					}
				}
				// Mover las balas y verificar si salen de la pantalla. Pueden OPTIMIZAR este ciclo si hacen las operaciones
				// en uno de los ciclos de arriba 
				for ( int k=listaBalas.size()-1; k>=0; k--) {
					Sprite bala = listaBalas.get(k);
					bala.setY(bala.getY()+3);
					if ( bala.getY()>ProyectoJuego.ALTO_CAMARA ) {
						listaBalas.remove(k);
						detachChild(bala);
					}
				}
				// Refrescar el tiempo en pantalla
				estado.actualizarTiempo(pSecondsElapsed);
			}
			
			@Override
			public void reset() {
			}
		});
	}

	/**
	 * Agrega el joystick virtual abajo a la izquierda
	 */
	private void agregarControles() {
		
		admRecursos.cargarRecursosControlAnalogo();
		this.controlVirtual = new AnalogOnScreenControl(
				0, 0, super.camara, admRecursos.mOnScreenControlBaseRegion,
				admRecursos.mOnScreenControlKnobRegion, 0.05f, 200, vbom,
				new IAnalogOnScreenControlListener() {
					@Override
					public void onControlChange(
							final BaseOnScreenControl pBaseOnScreenControl,
							final float pValueX, final float pValueY) {
						// Ahora se mueve con el SENSOR de GRAVEDAD
						/*
						if ( !pausado ) {
							personaje.setX(personaje.getX()+pValueX*10); // Mueve el personaje
						}*/
					}

					// DISPARO
					@Override
					public void onControlClick(
							final AnalogOnScreenControl pAnalogOnScreenControl) {
						if ( !pausado ) {
							disparar();
						}
					}
				});

		final Sprite controlBase = this.controlVirtual.getControlBase();
		controlBase.setAlpha(0.5f);
		controlBase.setOffsetCenter(0, 0);

		this.controlVirtual.getControlKnob().setScale(1.25f);

		this.setChildScene(controlVirtual);
	}	

	@Override
	public void onBackKeyPressed() {
		// ¡El usuario presionó el botón BACK!
		if (!pausado) {
			admEscenas.crearEscenaMenu();
			admEscenas.setEscena(TipoEscena.ESCENA_MENU);
			admEscenas.liberarEscenaJuego();
			camara.setHUD(null);
		} else {
			reanudarJuego();
		}
	}

	// Sale de la pausa, reanudar juego
	private void reanudarJuego() {
		camara.setHUD(estado); // Regresa el estado (marcador)
		controlVirtual.setVisible(true); // Regresa el joystick
		btnPausa.setVisible(true); // Regresa el botón de pausa
		EscenaJuego.this.detachChild(pantallaPausa); // QUITA la pantalla de pausa
		pausado = false;
	}

    @Override
    public TipoEscena getTipoEscena()
    {
    		return TipoEscena.ESCENA_JUEGO;
    }

	@Override
	public void liberarEscena() {
		this.detachSelf();
		this.dispose();
		// Deja de recibir las notifiacciones del acelerómetro
		mSensorManager.unregisterListener(this);
		// Salvar marcador más alto
		guardarMarcador();
	}

	// PREFERENCIAS: Almacena el marcador en las preferencias de las aplicaciones
	private void guardarMarcador() {
		SharedPreferences preferencias = actividad.getSharedPreferences(MARCADOR, 0);
		SharedPreferences.Editor editor = preferencias.edit();
		editor.putInt(MARCADOR, estado.getMarcador());
		// Guarda los cambios de manera permanente
		editor.commit();
	}

	/**
	 * 
	 */
	private void disparar() {
		Sprite bala = new Sprite(personaje.getX(),
				personaje.getY(), admRecursos.proyectilRegion, vbom);
		listaBalas.add(bala);	// Agrega la nueva bala a la lista...
		attachChild(bala);	// ...y la agrega a la escena también
	}
	
	// Recupera el sensor de gravedad y registra sus cambios
	private void iniciarSensorGravedad() {

		mSensorManager = (SensorManager) actividad
				.getSystemService(Context.SENSOR_SERVICE);	// Administrador de sensores
		sensorGravedad = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); // Se pide uno específico
		if (sensorGravedad != null) {	// Existe?
			// Encontró el sensor de GRAVEDAD, iniciar lecturas
			mSensorManager.registerListener(this, sensorGravedad, SensorManager.SENSOR_DELAY_GAME);
		} else {
			Log.d("SENSOR","NO HAY sensor de gravedad...");
		}
	}
	
	// Se ejecuta cada vez que hay un cambio en el sensor de gravedad
	@Override
	public void onSensorChanged(SensorEvent event) {
		// event.values es una arreglo de float de tamaño 3
		// los indices corresponden a la gravedad que se mide en x, y, z respectivamente 
		float nuevaX = personaje.getX()+event.values[1]*5;
		if ( !pausado && nuevaX<ProyectoJuego.ANCHO_CAMARA && nuevaX>=0 ) {
			personaje.setX(nuevaX);
		}
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {

	}
}
